/** 
 * View.java
 * @author Lex
 * 2007-8-28 ����08:11:43
 */
package fun.ui.canvas;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.RenderingHints;
import java.awt.Stroke;
import java.awt.geom.AffineTransform;
import java.awt.geom.Line2D;
import java.awt.geom.Path2D;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.List;

import org.lex.utils.collections.DoubleList;

import fun.symtable.UserSymbolTable;

/**
 * 
 */
public class View implements CanvasModelListener {
    private static int MIN_WIDTH = 20;
    private static int MIN_HEIGHT = 20;

    private int totalWidth = 1;
    private int totalHeight = 1;
    private int aWidth = 1;
    private int aHeight = 1;
    private Insets insets = new Insets(0, 0, 0, 0);
    // ===============================================
    private Color coordinate = Color.magenta;
    private Color foreground = Color.blue;
    private Color background = Color.white;
    // ===============================================
    private AffineTransform tx = null;
    private Stroke stroke = new BasicStroke(1);
    private BufferedImage image = null;
    private Graphics2D g2 = null;
    private Font font = null;
    // ===============================
    private double minX = -10D;
    private double maxX = 10D;
    private YAxisPolicy yAxisPolicy = YAxisPolicy.BIND_TO_X;
    private List<CanvasModel> models = null;
    private Path2D.Double curve = null;
    private DoubleList y = null;

    public View(Font font, int width, int height) {
	super();
	this.models = new ArrayList<CanvasModel>(3);
	this.curve = new Path2D.Double();
	y = new DoubleList();
	this.font = font;
	this.setSize(width, height);
    }

    // ============ getters & setters =============
    public Font getFont() {
	return this.font;
    }

    public void setFont(Font font) {
	this.font = font;
    }

    public Color getBackground() {
	return this.background;
    }

    public void setBackground(Color backgroundColor) {
	this.background = backgroundColor;
    }

    public Color getForeground() {
	return this.foreground;
    }

    public void setForeground(Color foregroundColor) {
	this.foreground = foregroundColor;
    }

    public Color getCoordinate() {
	return this.coordinate;
    }

    public void setCoordinate(Color coordinatesColor) {
	this.coordinate = coordinatesColor;
    }

    public YAxisPolicy getYAxisPolicy() {
	return this.yAxisPolicy;
    }

    public void setYAxisPolicy(YAxisPolicy axisPolicy) {
	this.yAxisPolicy = axisPolicy;
    }

    /**
     * @param width
     *            �ܹ���ȣ���������+�հ�����
     * @param height
     */
    public void setSize(int width, int height) {
	if (width < MIN_WIDTH)
	    width = MIN_WIDTH;
	if (height < MIN_HEIGHT)
	    height = MIN_HEIGHT;
	if (width == this.totalWidth && height == this.totalHeight)
	    return;
	this.totalHeight = height;
	this.totalWidth = width;
	// ==========================
	image = new BufferedImage(width, height, BufferedImage.TYPE_4BYTE_ABGR);
	g2 = image.createGraphics();
	g2.setStroke(this.stroke);
	g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
	// ==========================
	tx = new AffineTransform();
	tx.setTransform(1, 0, 0, -1, width / 2.0D, height / 2.0D);
	g2.setTransform(tx);
	// ==========================
	g2.setFont(this.getFont());
	int textHeight = g2.getFontMetrics().getHeight();
	this.insets.set(textHeight, textHeight * 3, textHeight, textHeight * 3);
	this.aHeight = height - insets.top - insets.bottom;
	this.aWidth = width - insets.left - insets.right;
	// ===============================================
	this.drawCoordinates();
    }

    public void modelChanged(CanvasModelEvent event) {
    }

    public BufferedImage getImage() {
	return this.image;
    }

    public void addModel(CanvasModel model) {
	model.addCanvasModelListener(this);
	models.add(model);
	computeModel(model, curve);
	g2.draw(curve);
    }

    public void removeModel(CanvasModel model) {
    }

    private void computeModel(CanvasModel model, Path2D.Double path) {
	path.reset();
	y.clear();
	double rangeX = maxX - minX;
	int x = aWidth / 2;
	if (1 == Integer.lowestOneBit(aWidth))// ����
	    x++;
	double minY = Double.MAX_VALUE;
	double maxY = Double.MIN_VALUE;
	for (int i = -x; i < x; i++) {
	    double value = this.function(model, rangeX * i / aWidth);
	    if (value < minY)
		minY = value;
	    if (value > maxY)
		maxY = value;
	    y.add(value);
	}
	// transform y
	if (yAxisPolicy == YAxisPolicy.AUTO_SCALE) {
	    double rangeY = maxY - minY;
	    for (int i = 0; i < y.size(); i++)
		y.set(i, y.get(i) * aHeight / rangeY);
	} else if (yAxisPolicy == YAxisPolicy.BIND_TO_X) {
	    for (int i = 0; i < y.size(); i++)
		y.set(i, y.get(i) * aWidth / rangeX);
	} else if (yAxisPolicy == YAxisPolicy.CUSTOM) {
	    // not implement yet
	}
	if (y.size() < 1)
	    return;
	path.moveTo(-x, y.get(0));
	// =======================
	for (int i = -x + 1, j = 1; i < x; i++)
	    path.lineTo(i, y.get(j++));
    }

    private double function(CanvasModel model, double value) {
	return model.getValue(UserSymbolTable.getInstance(), value);
    }

    // ============================
    private void drawCoordinates() {
	g2.setColor(coordinate);
	// x
	double x1 = -aWidth / 2, x2 = aWidth / 2, y1 = -aHeight / 2, y2 = y1;
	drawLine(x1, y1, x2, y2);
	// drawLine( g2, -aWidth / 2, 0, aWidth / 2, 0 );
	// drawLine( g2, aWidth / 2 - 12, 5, aWidth / 2, 0 );
	// drawLine( g2, aWidth / 2 - 12, -5, aWidth / 2, 0 );
	// y
	x2 = x1;
	y2 = -y2;
	drawLine(x1, y1, x2, y2);
	// drawLine( g2, 0, -aHeight / 2, 0, aHeight / 2 );
	// drawLine( g2, 5, aHeight / 2 - 12, 0, aHeight / 2 );
	// drawLine( g2, -5, aHeight / 2 - 12, 0, aHeight / 2 );
    }

    private Line2D tmpLine = new Line2D.Double();

    private void drawLine(double x1, double y1, double x2, double y2) {
	tmpLine.setLine(x1, y1, x2, y2);
	g2.draw(tmpLine);
    }
}
